﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

//// ReSharper disable CheckNamespace

namespace MugenInjection.Core.Components
//// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the class that can convert binding settings.
    /// </summary>
    public class SettingsWrapperConverter : ConverterBase
    {
        #region Fields

        private readonly NotifyCollectionImpl<Type> _settingWrapperTypes;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="BindingSettingsConverter" /> class.
        /// </summary>
        public SettingsWrapperConverter()
            : base(DefaultPriority, ServiceType.Complex | ServiceType.Activatable)
        {
            _settingWrapperTypes = new NotifyCollectionImpl<Type> { typeof(WithSettings<>) };
            _settingWrapperTypes.Added += SettingWrapperTypesOnAdded;
            _settingWrapperTypes.Changed += SettingWrapperTypesOnChanged;
            _settingWrapperTypes.Removed += SettingWrapperTypesOnRemoved;
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the collection of supported settings types.
        /// </summary>
        public IList<Type> SettingWrapperTypes
        {
            get { return _settingWrapperTypes; }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Occurs when settings type was added.
        /// </summary>
        protected virtual void SettingWrapperTypesOnAdded(Type type)
        {
            Validate.ArgumentNotNull(type, "type");
#if !NETFX_CORE
            if (!type.IsGenericType)
#else
            if (!type.GetTypeInfo().IsGenericType)            
#endif
                throw new ArgumentException("The setting type should be a generic.");

            Type genericType = type.MakeGenericType(typeof(BindingSettingsConverter));
            var constrTypes = new[]
                                  {
                                      typeof (BindingSettingsConverter),
                                      typeof (IDictionary<string, object>),
                                      typeof (IDictionary<string, object>)
                                  };
            if (genericType.GetConstructor(constrTypes) == null)
                throw new ArgumentException(
                    String.Format("The setting type should has constructor with types {0}",
                                  InjectorUtilsInternal.FormatTypes(constrTypes)), "type");
            OnSupportedTypeChanged(TypeChangedAction.Add, type);
        }

        /// <summary>
        ///     Occurs when settings type was changed.
        /// </summary>
        protected virtual void SettingWrapperTypesOnChanged(Type oldValue, Type newValue)
        {
            OnSupportedTypeChanged(TypeChangedAction.Remove, oldValue);
            SettingWrapperTypesOnAdded(newValue);
        }

        /// <summary>
        ///     Occurs when settings type was removed.
        /// </summary>
        protected virtual void SettingWrapperTypesOnRemoved(Type type)
        {
            OnSupportedTypeChanged(TypeChangedAction.Remove, type);
        }

        /// <summary>
        ///     Converts the specified object to setting wrapper.
        /// </summary>
        /// <param name="inputValue">
        ///     The specified object to convert, by default is always <see cref="IBindingContext" />.
        /// </param>
        /// <param name="originalType">The specified type of object.</param>
        /// <param name="createWithSettingsWrapper">The specified delegate to create managed-wrapper.</param>
        /// <returns>An instance of managed-wrapper.</returns>
        protected virtual object ConvertToSettingWrapper(object inputValue, Type originalType,
                                                       ActivatorDelegate createWithSettingsWrapper)
        {
            var converterContext = InjectorUtilsInternal.GetConverterContext(inputValue);
            var context = converterContext.BindingContext;
            context.Service = originalType;
            var instance = converterContext.Activator(context);
            IDictionary<string, object> settings;
            if (context.Binding != null)
                settings = context.Binding.Settings.ToDictionary();
            else if (converterContext.Binding != null)
                settings = converterContext.Binding.Settings.ToDictionary();
            else
                settings = InjectorUtilsInternal.ReadOnlyEmptyDictionary;
            return createWithSettingsWrapper(instance, settings, context.SpecialParameters);
        }

        #endregion

        #region Overrides of ConverterBase

        /// <summary>
        ///     Gets the supported types.
        /// </summary>
        public override IEnumerable<Type> SupportedTypes
        {
            get { return _settingWrapperTypes; }
        }

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert.</param>
        /// <param name="originalType">The result of convert.</param>
        /// <param name="convertAction">A delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        protected override ConverterResult TryConvertInternal(IBindingContext bindingContext, out Type originalType, out Func<object, object> convertAction)
        {
            var service = bindingContext.Service;
            originalType = null;
            convertAction = null;
#if !NETFX_CORE
            if (!service.IsGenericType)
#else
            if (!service.GetTypeInfo().IsGenericType)
#endif
                return ConverterResult.NotSupported;

            Type[] genericArguments = service.GetGenericArguments();
            if (genericArguments.Length != 1)
                return ConverterResult.NotSupported;

            bool isSupport = false;
            for (int index = 0; index < _settingWrapperTypes.Count; index++)
            {
                Type withSettingType = _settingWrapperTypes[index];
                if (!InjectorUtilsInternal.IsEqualGenericDefinition(service, withSettingType)) continue;
                isSupport = true;
                break;
            }
            if (!isSupport)
                return ConverterResult.NotSupported;

            originalType = genericArguments[0];
            var constrTypes = new[]
                                  {
                                      originalType,
                                      typeof (IDictionary<string, object>),
                                      typeof (IDictionary<string, object>)
                                  };


            ConstructorInfo constructor = service.GetConstructor(constrTypes);
            if (constructor == null)
                return ConverterResult.NotSupported;

            ActivatorDelegate activatorDelegate =
                InjectorUtils.ReflectionAccessProvider.GetActivatorDelegate(constructor);
            Type origType = originalType;
            convertAction = o => ConvertToSettingWrapper(o, origType, activatorDelegate);
            return ConverterResult.SupportedWithCache;
        }

        #endregion
    }

    /// <summary>
    ///     Represents the class that can convert binding settings.
    /// </summary>
    public class BindingSettingsConverter : ConverterBase
    {
        #region Fields

        private const string NotHandledKey = "~SettingsConverterNotHandled~";
        private static readonly Type[] SettingType = new[] { typeof(ISettings) };

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="BindingSettingsConverter" /> class.
        /// </summary>
        public BindingSettingsConverter()
            : base(DefaultPriority, ServiceType.Simple | ServiceType.Activatable)
        {
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Converts an object to an <see cref="ISettings" />.
        /// </summary>
        /// <param name="inputValue">
        ///     The specified object to convert, by default is always <see cref="ConverterContext" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="ISettings" />.
        /// </returns>
        protected virtual object ConvertToSettings(object inputValue)
        {
            var coverterContext = InjectorUtilsInternal.GetConverterContext(inputValue);
            var bindingContext = coverterContext.BindingContext;
            if (bindingContext == null)
                throw new ArgumentException("It is imposible to get ISettings from empty binding context.");

            bindingContext = bindingContext.ParentBindingContext;
            if (bindingContext == null || bindingContext.Binding == null)
                throw new ArgumentException("It is imposible to get ISettings from empty parent binding context.",
                                            new BindingNotFoundException(bindingContext));
            return bindingContext.Binding.Settings;
        }

        /// <summary>
        ///     Converts an object to an <see cref="ISettings" />.
        /// </summary>
        /// <param name="inputValue">
        ///     The specified object to convert, by default is always <see cref="ConverterContext" />.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="ISettings" />.
        /// </returns>
        protected virtual object ConvertToDictionary(object inputValue)
        {
            ConverterContext coverterContext = InjectorUtilsInternal.GetConverterContext(inputValue);
            var context = coverterContext.BindingContext;
            if (context == null)
                throw new ArgumentException("It is imposible to get ISettings from empty binding context.");

            context.Service = typeof(IDictionary<string, object>);
            if (context.CallInjector.CanResolve(context, true, false))
            {
                var dictionary = InjectorUtilsInternal.GetDictionary(context);
                dictionary[NotHandledKey] = true;
                return context.CallInjector.Resolve(context);
            }

            context = context.ParentBindingContext;
            if (context == null || context.Binding == null)
                throw new ArgumentException("It is imposible to get ISettings from empty parent binding context.",
                                            new BindingNotFoundException(context));
            return context.Binding.Settings.ToDictionary();
        }

        #endregion

        #region Overrides of ConverterBase

        /// <summary>
        ///     Gets the supported types.
        /// </summary>
        public override IEnumerable<Type> SupportedTypes
        {
            get { return SettingType; }
        }

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert.</param>
        /// <param name="originalType">The result of convert.</param>
        /// <param name="convertAction">A delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        protected override ConverterResult TryConvertInternal(IBindingContext bindingContext, out Type originalType, out Func<object, object> convertAction)
        {
            var service = bindingContext.Service;
            if (service == typeof(ISettings))
            {
                originalType = service;
                convertAction = ConvertToSettings;
                return ConverterResult.SupportedWithCache;
            }
            if (service == typeof(IDictionary<string, object>) && !bindingContext.SpecialParameters.ContainsKey(NotHandledKey))
            {
                originalType = service;
                convertAction = ConvertToDictionary;
                return ConverterResult.SupportedWithoutCache;
            }
            originalType = null;
            convertAction = null;
            return ConverterResult.NotSupported;
        }

        #endregion
    }
}